package org.wsiz.vro.domain;

import static javax.persistence.CascadeType.PERSIST;
import static javax.persistence.EnumType.STRING;
import static javax.persistence.FetchType.LAZY;
import static javax.persistence.TemporalType.TIMESTAMP;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.Transient;
import javax.persistence.Version;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlTransient;

import org.apache.log4j.Logger;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.data.domain.Persistable;
import org.wsiz.vro.domain.enums.Civility;

import com.google.common.base.Objects;

/**
 * @author "Tomasz Marciniak"
 */
@Entity
@Table(name = "account")
public class Account implements Persistable<Integer> {
    private static final long serialVersionUID = 1L;
 
    @SuppressWarnings("unused")
    private static final Logger log = Logger.getLogger(Account.class);

    private Integer id; 
    private String login; 
    private String password; 
    private String email;
    private Boolean isEnabled;
    private Civility civility;
    private String firstName;
    private String lastName;
    private Date birthDate;
    private Integer version;

    private Integer addressId;
    private Address address; // (addressId)

    private List<Role> roles = new ArrayList<Role>();

    public Account() {
    }

    public Account(Integer primaryKey) {
        setId(primaryKey);
    }

    @Override
    @Transient
    @XmlTransient
    public boolean isNew() {
        return getId() == null;
    }

    /**
     * Returns the granted authorities for this user. You may override
     * this method to provide your own custom authorities.
     */
    @Transient
    @XmlTransient
    public List<String> getRoleNames() {
        List<String> roleNames = new ArrayList<String>();

        for (Role role : getRoles()) {
            roleNames.add(role.getRoleName());
        }

        return roleNames;
    }

    @Column(nullable = false, unique = true, precision = 10)
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Id
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @NotEmpty
    @Size(max = 255)
    @Column(nullable = false, unique = true)
    public String getLogin() {
        return login;
    }

    public void setLogin(String login) {
        this.login = login;
    }

    @NotEmpty
    @Size(max = 255)
    @Column(nullable = false)
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @NotEmpty
    @Size(max = 255)
    @Email
    @Column(nullable = false, unique = true)
    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Column(name = "is_enabled", length = 1)
    public Boolean getIsEnabled() {
        return isEnabled;
    }

    public void setIsEnabled(Boolean isEnabled) {
        this.isEnabled = isEnabled;
    }

    @Column(length = 2)
    @Enumerated(STRING)
    public Civility getCivility() {
        return civility;
    }

    public void setCivility(Civility civility) {
        this.civility = civility;
    }

    @Size(max = 255)
    @Column(name = "first_name")
    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    @Size(max = 255)
    @Column(name = "last_name")
    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    @Column(name = "birth_date", length = 29)
    @Temporal(TIMESTAMP)
    public Date getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(Date birthDate) {
        this.birthDate = birthDate;
    }

    @Column(name = "address_id", precision = 10, insertable = false, updatable = false)
    public Integer getAddressId() {
        return addressId;
    }

    private void setAddressId(Integer addressId) {
        this.addressId = addressId;
    }

    @Column(precision = 10)
    @Version
    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

    @JoinColumn(name = "address_id")
    @ManyToOne(cascade = PERSIST, fetch = LAZY)
    public Address getAddress() {
        return address;
    }

    /**
     * Set the address without adding this Account instance on the passed address
     * If you want to preserve referential integrity we recommend to use
     * instead the corresponding adder method provided by {@link Address}
     */
    public void setAddress(Address address) {
        this.address = address;

        // We set the foreign key property so it can be used by Hibernate search by Example facility.
        if (address != null) {
            setAddressId(address.getId());
        } else {
            setAddressId(null);
        }
    }

    /**
     * Returns the roles list.
     */
    @JoinTable(name = "account_role", joinColumns = @JoinColumn(name = "account_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
    @ManyToMany(cascade = PERSIST)
    public List<Role> getRoles() {
        return roles;
    }

    /**
     * Set the roles list.
     * <p>
     * It is recommended to use the helper method {@link #addRole(Role)} / {@link #removeRole(Role)}
     * if you want to preserve referential integrity at the object level.
     *
     * @param roles the list of Role
     */
    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    /**
     * Helper method to add the passed {@link Role} to the roles list.
     */
    public boolean addRole(Role role) {
        return getRoles().add(role);
    }

    /**
     * Helper method to remove the passed {@link Role} from the roles list.
     */
    public boolean removeRole(Role role) {
        return getRoles().remove(role);
    }

    /**
     * Helper method to determine if the passed {@link Role} is present in the roles list.
     */
    public boolean containsRole(Role role) {
        return getRoles() != null && getRoles().contains(role);
    }

    /**
     * Set the default values.
     */
    public void initDefaultValues() {
    }

    @Override
    public boolean equals(Object other) {
        return this == other || (other instanceof Account && hashCode() == other.hashCode());
    }

    /**
     * hashCode implementation using a business key.
     */
    @Override
    public int hashCode() {
        return Objects.hashCode(getEmail());
    }

    /**
     * Construct a readable string representation for this {@link Account} instance.
     */
    @Override
    public String toString() {
        return Objects.toStringHelper(this) //
                .add("id", getId()) //
                .add("login", getLogin()) //
                .add("password", getPassword()) //
                .add("email", getEmail()) //
                .add("isEnabled", getIsEnabled()) //
                .add("civility", getCivility()) //
                .add("firstName", getFirstName()) //
                .add("lastName", getLastName()) //
                .add("birthDate", getBirthDate()) //
                .add("addressId", getAddressId()) //
                .add("version", getVersion()) //
                .toString();
    }
}